/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Eclipse Public License, Version 1.0 (the "License"); you
 * may not use this file except in compliance with the License. You may obtain a
 * copy of the License at
 *
 *      http://www.eclipse.org/org/documents/epl-v10.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package org.eclipse.andmore.integration.tests.functests.sampleProjects;

import static org.junit.Assert.*;

import com.android.SdkConstants;

import org.eclipse.andmore.AdtUtils;
import org.eclipse.andmore.integration.tests.SdkLoadingTestCase;
import org.eclipse.andmore.internal.wizards.newproject.NewProjectCreator;
import org.eclipse.andmore.internal.wizards.newproject.NewProjectWizardState;
import org.eclipse.andmore.internal.wizards.newproject.NewProjectWizardState.Mode;

import com.android.sdklib.IAndroidTarget;

import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jface.operation.IRunnableContext;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.swt.widgets.Display;
import org.junit.Ignore;
import org.junit.Test;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Test case that verifies all SDK sample projects can be imported, and built in
 * Eclipse.
 * <p/>
 * TODO: add support for deploying apps onto emulator and verifying successful
 * execution there
 *
 */
@Ignore
public class SampleProjectTest extends SdkLoadingTestCase {

	private static final Logger sLogger = Logger.getLogger(SampleProjectTest.class.getName());

	/**
	 * Finds all samples projects in set SDK and verify they can be built in
	 * Eclipse.
	 * <p/>
	 * TODO: add install and run on emulator test
	 * 
	 * @throws CoreException
	 */
	@Test
	public void testSamples() throws CoreException {
		// TODO: For reporting purposes, it would be better if a separate test
		// success or failure
		// could be reported for each sample
		for (IAndroidTarget target : getSdk().getTargets()) {
			doTestSamplesForTarget(target);
		}
	}

	private void doTestSamplesForTarget(IAndroidTarget target) throws CoreException {
		String path = target.getPath(IAndroidTarget.SAMPLES);
		File samples = new File(path);
		if (samples.isDirectory()) {
			File[] files = samples.listFiles();
			for (File file : files) {
				if (file.isDirectory()) {
					doTestSampleProject(file.getName(), file.getAbsolutePath(), target);
				}
			}
		}
	}

	/**
	 * Tests the sample project with the given name
	 *
	 * @param target
	 *            - SDK target of project
	 * @param name
	 *            - name of sample project to test
	 * @param path
	 *            - absolute file system path
	 * @throws CoreException
	 */
	private void doTestSampleProject(String name, String path, IAndroidTarget target) throws CoreException {
		IProject iproject = null;
		try {
			sLogger.log(Level.INFO, String.format("Testing sample %s for target %s", name, target.getName()));

			prepareProject(path, target);

			IRunnableContext context = new IRunnableContext() {
				@Override
				public void run(boolean fork, boolean cancelable, IRunnableWithProgress runnable)
						throws InvocationTargetException, InterruptedException {
					runnable.run(new NullProgressMonitor());
				}
			};
			NewProjectWizardState state = new NewProjectWizardState(Mode.SAMPLE);
			state.projectName = name;
			state.target = target;
			state.packageName = "com.android.samples";
			state.activityName = name;
			state.applicationName = name;
			state.chosenSample = new File(path);
			state.useDefaultLocation = false;
			state.createActivity = false;

			NewProjectCreator creator = new NewProjectCreator(state, context);
			creator.createAndroidProjects();
			iproject = validateProjectExists(name);
			validateNoProblems(iproject);
		} catch (CoreException e) {
			sLogger.log(Level.SEVERE, String.format("Unexpected exception when creating sample project %s "
					+ "for target %s", name, target.getName()));
			throw e;
		} finally {
			if (iproject != null) {
				iproject.delete(false, true, new NullProgressMonitor());
			}
		}
	}

	private void prepareProject(String path, IAndroidTarget target) {
		if (target.getVersion().isPreview()) {
			// need to explicitly set preview's version in manifest for project
			// to compile
			final String manifestPath = path + File.separatorChar + SdkConstants.FN_ANDROID_MANIFEST_XML;
			AndroidManifestWriter manifestWriter = AndroidManifestWriter.parse(manifestPath);
			assertNotNull(String.format("could not read manifest %s", manifestPath), manifestWriter);
			assertTrue(manifestWriter.setMinSdkVersion(target.getVersion().getApiString()));
		}
	}

	private IProject validateProjectExists(String name) {
		IProject iproject = getIProject(name);
		assertTrue(String.format("%s project not created", name), iproject.exists());
		assertTrue(String.format("%s project not opened", name), iproject.isOpen());
		return iproject;
	}

	private IProject getIProject(String name) {
		IProject iproject = ResourcesPlugin.getWorkspace().getRoot().getProject(name);
		return iproject;
	}

	private void validateNoProblems(IProject iproject) throws CoreException {
		waitForBuild(iproject);

		boolean hasErrors = false;
		StringBuilder failureBuilder = new StringBuilder(String.format("%s project has errors:", iproject.getName()));
		IMarker[] markers = iproject.findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE);
		if (markers != null && markers.length > 0) {
			// the project has marker(s). even though they are "problem" we
			// don't know their severity. so we loop on them and figure if they
			// are warnings or errors
			for (IMarker m : markers) {
				int s = m.getAttribute(IMarker.SEVERITY, -1);
				if (s == IMarker.SEVERITY_ERROR) {
					hasErrors = true;
					failureBuilder.append("\n");
					failureBuilder.append(m.getAttribute(IMarker.MESSAGE, ""));
				}
			}
		}
		failureBuilder.append("Project location: " + AdtUtils.getAbsolutePath(iproject));
		assertFalse(failureBuilder.toString(), hasErrors);
	}

	/**
	 * Waits for build to complete.
	 *
	 * @param iproject
	 */
	private void waitForBuild(final IProject iproject) {

		final BuiltProjectDeltaVisitor deltaVisitor = new BuiltProjectDeltaVisitor(iproject);
		IResourceChangeListener newBuildListener = new IResourceChangeListener() {

			@Override
			public void resourceChanged(IResourceChangeEvent event) {
				try {
					event.getDelta().accept(deltaVisitor);
				} catch (CoreException e) {
					fail();
				}
			}

		};
		iproject.getWorkspace().addResourceChangeListener(newBuildListener, IResourceChangeEvent.POST_BUILD);

		// poll build listener to determine when build is done
		// loop max of 1200 times * 50 ms = 60 seconds
		final int maxWait = 1200;
		for (int i = 0; i < maxWait; i++) {
			if (deltaVisitor.isProjectBuilt()) {
				return;
			}
			try {
				Thread.sleep(50);
			} catch (InterruptedException e) {
				// ignore
			}
			if (Display.getCurrent() != null) {
				Display.getCurrent().readAndDispatch();
			}
		}

		sLogger.log(Level.SEVERE, "expected build event never happened?");
		fail(String.format("Expected build event never happened for %s", iproject.getName()));
	}

	/**
	 * Scans a given IResourceDelta looking for a "build event" change for given
	 * IProject
	 *
	 */
	private class BuiltProjectDeltaVisitor implements IResourceDeltaVisitor {

		private IProject mIProject;
		private boolean mIsBuilt;

		public BuiltProjectDeltaVisitor(IProject iproject) {
			mIProject = iproject;
			mIsBuilt = false;
		}

		@Override
		public boolean visit(IResourceDelta delta) {
			if (mIProject.equals(delta.getResource())) {
				setBuilt(true);
				return false;
			}
			return true;
		}

		private synchronized void setBuilt(boolean b) {
			mIsBuilt = b;
		}

		public synchronized boolean isProjectBuilt() {
			return mIsBuilt;
		}
	}
}
